﻿#include "player.h"
#include "prepare_worker.h"
#include "play_worker.h"
#include "seek_worker.h"
#include "stdlib.h"

using namespace v8;

Nan::Persistent<Function> Player::constructor;
std::vector<Player *> Player::list;
SDL_mutex *Player::mutex = NULL;

NAN_MODULE_INIT(Player::Init)
{
  Local<FunctionTemplate> tpl = Nan::New<FunctionTemplate>(Player::New);
  tpl->SetClassName(Nan::New<String>("Player").ToLocalChecked());
  tpl->InstanceTemplate()->SetInternalFieldCount(1);
  Nan::SetPrototypeMethod(tpl, "prepare", Player::Prepare);
  Nan::SetPrototypeMethod(tpl, "play", Player::Play);
  Nan::SetPrototypeMethod(tpl, "pause", Player::Pause);
  Nan::SetPrototypeMethod(tpl, "resume", Player::Resume);
  Nan::SetPrototypeMethod(tpl, "stop", Player::Stop);
  Nan::SetPrototypeMethod(tpl, "setTempo", Player::SetTempo);
  Nan::SetPrototypeMethod(tpl, "setPitch", Player::SetPitch);
  Nan::SetPrototypeMethod(tpl, "seek", Player::Seek);
  Nan::SetAccessor(tpl->InstanceTemplate(), Nan::New<String>("duration").ToLocalChecked(), Player::Duration);
  Nan::SetAccessor(tpl->InstanceTemplate(), Nan::New<String>("state").ToLocalChecked(), Player::State);
  Nan::SetAccessor(tpl->InstanceTemplate(), Nan::New<String>("currentPos").ToLocalChecked(), Player::CurrentPos);
  Nan::SetAccessor(tpl->InstanceTemplate(), Nan::New<String>("volume").ToLocalChecked(), Player::Volume, Player::SetVolume);
  Local<Function> cons = Nan::GetFunction(tpl).ToLocalChecked();
  constructor.Reset(cons);
  Nan::Set(target, Nan::New<String>("Player").ToLocalChecked(), cons);
  av_log_set_level(AV_LOG_FATAL);
  SDL_Init(SDL_INIT_AUDIO);
  mutex = SDL_CreateMutex();
  //转为 2channel 44100Hz,1152,float 播放
  SDL_AudioSpec audioSpec;
  memset(&audioSpec, 0, sizeof(SDL_AudioSpec));
  audioSpec.channels = 2;
  audioSpec.format = AUDIO_F32SYS;
  audioSpec.freq = 44100;
  audioSpec.samples = 1152;
  audioSpec.silence = 0;
  audioSpec.callback = Player::FeedAudio;
  audioSpec.userdata = NULL;
  SDL_OpenAudio(&audioSpec, NULL);
  SDL_PauseAudio(0);
}
void Player::FeedAudio(void *userData, uint8_t *buffer, int len)
{
  SDL_memset(buffer, 0, len);
  SDL_LockMutex(mutex);
  std::vector<Player *>::iterator p;
  for (p = list.begin(); p != list.end(); p++)
  {
    // printf("seeked = %s , freshbuf = %s\n", (*p)->mSeeked ? "true" : "false", (*p)->mFreshBuf ? "true" : "false");

    SDL_LockMutex((*p)->mBufferMutex);
    if ((*p)->mFreshBuf)
    {
      (*p)->mFreshBuf = false;
      if (!(*p)->mSeeked)
        SDL_MixAudio(buffer, (*p)->mPcmBuf, len, (*p)->mVolume);
      else
      {
        SDL_LockMutex((*p)->mCtrlMutex);
        (*p)->mSeekedCnt++;
        SDL_UnlockMutex((*p)->mCtrlMutex);
        if ((*p)->mSeekedCnt > 5) // WTF 出此下策。。。
        {
          (*p)->mSeeked = false;
        }
      }
    }
    SDL_UnlockMutex((*p)->mBufferMutex);

    SDL_CondSignal((*p)->mCond);
  }
  SDL_UnlockMutex(mutex);
}
Player::Player()
{
  mCond = SDL_CreateCond();
  mCondMutex = SDL_CreateMutex();
  mCtrlMutex = SDL_CreateMutex();
  mBufferMutex = SDL_CreateMutex();
  mSeekMutex = SDL_CreateMutex();
  mLastFrameConduv = new uv_cond_t;
  uv_cond_init(mLastFrameConduv);
  mVolume = SDL_MIX_MAXVOLUME;
  mStretcher = new RubberBand::RubberBandStretcher(44100, 2, RubberBand::RubberBandStretcher::OptionProcessRealTime, 1, 1);
  mPcmBuf = (uint8_t *)malloc(2 * sizeof(float) * mSamplePerFrame);
  memset(mPcmBuf, 0, 2 * sizeof(float) * mSamplePerFrame);
  for (int i = 0; i < 2; i++)
  {
    mLeftBuf[i] = (float *)malloc(sizeof(float) * mSamplePerFrame);
  }
  SDL_LockMutex(mutex);
  list.push_back(this);
  SDL_UnlockMutex(mutex);
}

Player::~Player()
{
  printf("~Player()\n");
  SDL_LockMutex(mutex);
  list.erase(std::remove(list.begin(), list.end(), this), list.end());
  SDL_UnlockMutex(mutex);
  uv_cond_destroy(mLastFrameConduv);
  delete mLastFrameConduv;
  SDL_DestroyCond(mCond);
  SDL_DestroyMutex(mCondMutex);
  SDL_DestroyMutex(mCtrlMutex);
  SDL_DestroyMutex(mBufferMutex);
  SDL_DestroyMutex(mSeekMutex);
  if (mFmtCtx)
  {
    avformat_close_input(&mFmtCtx);
  }
  if (mDecoderCtx)
  {
    avcodec_close(mDecoderCtx);
  }
  if (mPacket)
  {
    av_packet_free(&mPacket);
  }
  if (mFrame)
  {
    av_frame_free(&mFrame);
  }
  if (mSwr)
  {
    swr_close(mSwr);
    swr_free(&mSwr);
  }
  if (mFrameAsync)
  {
    if (mFrameAsync->data)
    {
      delete mFrameAsync->data;
    }
    delete mFrameAsync;
  }
  delete mStretcher;
  delete mPcmBuf;
  for (int i = 0; i < 2; i++)
  {
    delete mLeftBuf[i];
  }
}
NAN_METHOD(Player::New)
{
  if (info.IsConstructCall())
  {
    Player *player = new Player();
    player->Wrap(info.This());
    info.GetReturnValue().Set(info.This());
  }
  else
  {
    Local<Function> cons = Nan::New<Function>(constructor);
    Local<Object> ret = Nan::NewInstance(cons).ToLocalChecked();
    info.GetReturnValue().Set(ret);
  }
}
NAN_METHOD(Player::Prepare)
{
  /*
  function prepare(url:string,onPrepare:(err:Error?)=>void):void;
  */
  Local<String> url = Nan::To<String>(info[0]).ToLocalChecked();
  Local<Function> onPrepare = Nan::To<Function>(info[1]).ToLocalChecked();
  Nan::Callback *onPrepareCallback = new Nan::Callback(onPrepare);
  Player *player = Nan::ObjectWrap::Unwrap<Player>(info.Holder());
  player->mReadyFlag = false;
  player->RefPlayer();
  Nan::AsyncQueueWorker(new PrepareWorker(url, player, onPrepareCallback));
}

NAN_METHOD(Player::Pause)
{
  /*
  function pause():void
  */
  Player *player = Nan::ObjectWrap::Unwrap<Player>(info.Holder());
  if (player->mReadyFlag)
  {
    if (!player->mPauseFlag)
    {
      player->mPauseFlag = true;
      SDL_LockMutex(player->mCtrlMutex);
    }
  }
}

NAN_METHOD(Player::Resume)
{
  /*
  function resume():void
  */
  Player *player = Nan::ObjectWrap::Unwrap<Player>(info.Holder());
  if (player->mReadyFlag)
  {
    if (player->mPauseFlag)
    {
      player->mPauseFlag = false;
      SDL_UnlockMutex(player->mCtrlMutex);
    }
  }
}

NAN_METHOD(Player::SetTempo)
{
  /*
  function setTempo(ratio:number):void
  */
  Player *player = Nan::ObjectWrap::Unwrap<Player>(info.Holder());
  double tempo = Nan::To<double>(info[0]).ToChecked();
  SDL_LockMutex(player->mCtrlMutex);
  player->mStretcher->setTimeRatio(1 / tempo);
  SDL_UnlockMutex(player->mCtrlMutex);
}
NAN_METHOD(Player::SetPitch)
{
  /*
  function setPitch(minor:number):void
  */
  Player *player = Nan::ObjectWrap::Unwrap<Player>(info.Holder());
  double minor = Nan::To<double>(info[0]).ToChecked();
  SDL_LockMutex(player->mCtrlMutex);
  player->mStretcher->setPitchScale(pow(2.0, minor / 12.0));
  SDL_UnlockMutex(player->mCtrlMutex);
}

NAN_METHOD(Player::Seek)
{
  /*
  function seek(time:number,onSeek:()=>void):void;
  */

  Player *player = Nan::ObjectWrap::Unwrap<Player>(info.Holder());
  if (player->mReadyFlag && !player->mPlayEndFlag)
  {
    double time = Nan::To<double>(info[0]).ToChecked();
    Nan::Callback *callback = new Nan::Callback(Nan::To<Function>(info[1]).ToLocalChecked());
    int64_t seekPos = 0;
    if (time <= 0)
    {
      seekPos = 0;
    }
    else if (time >= (double)player->mFmtCtx->duration / AV_TIME_BASE)
    {
      double t = (double)player->mFmtCtx->duration / AV_TIME_BASE;
      seekPos = (int64_t)(t / av_q2d(player->mFmtCtx->streams[player->mAudioIdx]->time_base));
    }
    else
    {
      seekPos = (int64_t)(time / av_q2d(player->mFmtCtx->streams[player->mAudioIdx]->time_base));
    }
    player->RefPlayer();
    Nan::AsyncQueueWorker(new SeekWorker(callback, player, seekPos));
  }
}

void Player::FrameAsyncCallback(uv_async_t *arg)
{
  Nan::HandleScope scope;
  FrameCallbackData *data = reinterpret_cast<FrameCallbackData *>(arg->data);
  Player *player = data->mPlayer;
  Nan::Callback *callback = data->mCallback;
  double duration = (double)player->mFmtCtx->duration / AV_TIME_BASE;
  double currentPos = player->mLastCurrentPos;
  Local<Object> obj = Nan::New<Object>();
  Nan::Set(obj, Nan::New<String>("duration").ToLocalChecked(), Nan::New<Number>(duration));
  Nan::Set(obj, Nan::New<String>("currentPos").ToLocalChecked(), Nan::New<Number>(currentPos));
  Local<Value> argv[1] = {obj};
  Nan::Call(*callback, 1, argv);
  if (player->mPlayEndFlag)
  {
    uv_cond_signal(player->mLastFrameConduv);
  }
}

NAN_GETTER(Player::Duration)
{
  Player *player = Nan::ObjectWrap::Unwrap<Player>(info.Holder());
  if (player->mReadyFlag)
  {
    info.GetReturnValue().Set((double)player->mFmtCtx->duration / AV_TIME_BASE);
  }
}

NAN_GETTER(Player::State)
{
  Player *player = Nan::ObjectWrap::Unwrap<Player>(info.Holder());
  if (player->mPlayEndFlag)
  {
    info.GetReturnValue().Set(Nan::New<String>("end").ToLocalChecked());
  }
  else if (!player->mReadyFlag)
  {
    info.GetReturnValue().Set(Nan::New<String>("preparing").ToLocalChecked());
  }
  else
  {
    if (!player->mPlayFlag)
    {
      info.GetReturnValue().Set(Nan::New<String>("ready").ToLocalChecked());
    }
    else
    {
      if (player->mSeeking)
      {
        info.GetReturnValue().Set(Nan::New<String>("seeking").ToLocalChecked());
      }
      else
      {

        if (player->mPauseFlag)
        {
          info.GetReturnValue().Set(Nan::New<String>("paused").ToLocalChecked());
        }
        else
        {
          info.GetReturnValue().Set(Nan::New<String>("running").ToLocalChecked());
        }
      }
    }
  }
}

NAN_GETTER(Player::CurrentPos)
{
  Player *player = Nan::ObjectWrap::Unwrap<Player>(info.Holder());
  if (!player->mPlayEndFlag)
  {
    info.GetReturnValue().Set(player->mLastCurrentPos);
  }
  else
  {

    info.GetReturnValue().Set((double)player->mFmtCtx->duration / AV_TIME_BASE);
  }
}

NAN_GETTER(Player::Volume)
{
  Player *player = Nan::ObjectWrap::Unwrap<Player>(info.Holder());
  info.GetReturnValue().Set(player->mVolume);
}
NAN_SETTER(Player::SetVolume)
{
  Player *player = Nan::ObjectWrap::Unwrap<Player>(info.Holder());
  int v = Nan::To<int>(value).ToChecked();
  if (v >= 0 && v <= SDL_MIX_MAXVOLUME)
  {
    SDL_LockMutex(player->mCtrlMutex);
    player->mVolume = v;
    SDL_UnlockMutex(player->mCtrlMutex);
  }
}

NAN_METHOD(Player::Stop)
{
  /*
  function stop():void
  */
  Player *player = Nan::ObjectWrap::Unwrap<Player>(info.Holder());
  player->mStopFlag = true;
  SDL_UnlockMutex(player->mCtrlMutex);
}
NAN_METHOD(Player::Play)
{
  /*
  interface FrameDetail{
    currentPos:number,
    duration:number
  }
  function play(onFrame:(frame:FrameDetail)=>void,onEnd:()=>void):void
  */
  Player *player = Nan::ObjectWrap::Unwrap<Player>(info.Holder());
  if (!player->mReadyFlag)
  {
    Nan::ThrowError("media is not prepared");
    return;
  }
  player->mFrameAsync = new uv_async_t();
  player->mFrameAsync->data = new FrameCallbackData(player, new Nan::Callback(Nan::To<Function>(info[0]).ToLocalChecked()));
  uv_async_init(uv_default_loop(), player->mFrameAsync, Player::FrameAsyncCallback);
  Nan::Callback *callback = new Nan::Callback(Nan::To<Function>(info[1]).ToLocalChecked());
  player->RefPlayer();
  Nan::AsyncQueueWorker(new PlayWorker(player, callback));
}
void Player::setPcmBuf(int channel_no, int index, float value)
{
  if (mPcmBuf)
  {
    if (channel_no == 1)
    {
      memcpy(((float *)mPcmBuf) + index * 2, &value, sizeof(float));
    }
    else if (channel_no == 2)
    {
      memcpy(((float *)mPcmBuf) + index * 2 + 1, &value, sizeof(float));
    }
  }
}

void Player::RefPlayer(){
  this->Ref();
}

void Player::UnrefPlayer(){
  this->Unref();
}